﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Microsoft.Scripting.Utils;

using Scala.Compiler.Ast;
using Scala.Compiler.Binding;
using System.Reflection;
using Scala.Compiler.Types;

namespace Scala.Compiler
{
    public class ScalaTyper : ScalaWalker
    {
        private ScalaAst _globalAst;
        internal ScalaScope _currentScope;
        private List<ScalaScope> _scopes = new List<ScalaScope>();

        private ScalaTyper(ScalaAst ast)
        {
            ContractUtils.RequiresNotNull(ast, "ast");

            _globalAst = ast;
        }

        #region Public Surface

        internal static void TypeAst(ScalaAst ast)
        {
            Assert.NotNull(ast);
            ScalaTyper typer = new ScalaTyper(ast);
            typer.Type(ast);
        }

        #endregion

        private void Type(ScalaAst unboundAst)
        {
            Assert.NotNull(unboundAst);

            unboundAst.Walk(this);


        }

        #region AstBinder Overrides

        public override bool Walk(PackageDefStatement node)
        {
            node.Scope.PackageString += "." + node.PId;
            return true;
        }

        public override void PostWalk(PackageDefStatement node)
        {
            _currentScope = node.Scope.ParentScope;
            base.PostWalk(node);
        }

        public override bool Walk(ModuleDefStatement node)
        {
            _currentScope = node.Scope;
            if (_currentScope.CurrentType == null)
            {
                throw new Exception(String.Format("The Type {0} is not present in the Scope {1}", node.Name, _currentScope.ParentScope.Name));
            }
            
            ///// Temp
            var temp = new DefDefStatement("constr", new List<Parameter[]>());
            var func = _currentScope.EnsureFunction("constr", temp);
            func.IsConstructor = true;
            func.ConstructorBuilder = node.Scope.CurrentType.TypeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, System.Type.EmptyTypes);

            /////
            node.Body.Walk(this);
            return false;
        }

        public override void PostWalk(ModuleDefStatement node)
        {
            _currentScope = node.Scope.ParentScope;
            base.PostWalk(node);
        }

        public override bool Walk(ClassDefStatement node)
        {
            _currentScope = node.Scope;
            if (_currentScope.CurrentType == null)
            {
                throw new Exception(String.Format("The Type {0} is not present in the Scope {1}", node.Name, _currentScope.ParentScope.Name));
            }
            
            
            ///// Temp
            var temp = new DefDefStatement("constr", new List<Parameter[]>());
            var func = _currentScope.EnsureFunction("constr", temp);
            func.IsConstructor = true;
            func.ConstructorBuilder = node.Scope.CurrentType.TypeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.HasThis, System.Type.EmptyTypes);

            /////
            node.Body.Walk(this);
            return false;
        }

        public override void PostWalk(ClassDefStatement node)
        {
            _currentScope = node.Scope.ParentScope;
            base.PostWalk(node);
        }

        public override bool Walk(DefDefStatement node)
        {
            _currentScope = node.Scope;
            if (node.TypeName == null)
            {
                throw new Exception(String.Format("Unable to determine the return Type of the function {0}", node.Name));
            }
            
            ScalaFunction func;
            if (!_currentScope.ParentScope.TryGetAnyFunc(node.Name, out func))
            {
                throw new Exception(String.Format("Undefined Function {0} in the Scope {1}", node.Name, _currentScope.ParentScope));
            }
            if (func.IsConstructor)
            {
                func.ConstructorBuilder = _currentScope.CurrentType.TypeBuilder.DefineConstructor(func.GetAttributes(), CallingConventions.HasThis, func.GetParamTypes());
            }
            else
            {
                func.MethodBuilder = _currentScope.CurrentType.TypeBuilder.DefineMethod(node.Name, func.GetAttributes(), CallingConventions.HasThis, func.ReturnType, func.GetParamTypes());
            }
            node.Body.Walk(this);
            return false;
        }

        public override void PostWalk(DefDefStatement node)
        {
            _currentScope = node.Scope.ParentScope;
            base.PostWalk(node);
        }

        public override bool Walk(ValDefStatement node)
        {
            node.Rhs.Walk(this);
            if (node.Rhs.TypeName == null)
            {
                throw new Exception(String.Format("Unable to determine the Type of value {0}", node.Name));
            }
            node.TypeName = node.Rhs.TypeName;
            
            _currentScope = node.Scope;
            ScalaVariable variable;
            if (!(_currentScope.TryGetAnyVariable(node.Name, out variable)))
            {
                throw new Exception(String.Format("Variable {0} not found in the Scope {1}", node.Name, _currentScope.Name));
            }
            variable.TypeName = node.TypeName;
            if (variable.IsProperty)
            {
                Type propRetType;
                if (!InternalTypes.GetType(variable.TypeName, variable.Scope, out propRetType))
                {
                    throw new Exception(String.Format("Undefined Return Type {0} of the Variable {1} in the Scope {2}", variable.TypeName, variable.Name, variable.Scope));
                }
                variable.PropertyBuilder = NewTypeMaker.CreateProperty(variable.Scope.CurrentType.TypeBuilder, variable.Name, propRetType, PropertyAttributes.SpecialName);
            }
            
            variable.TypeName = node.TypeName;
            
            return true;
        }

        public override void PostWalk(ValDefStatement node)
        {
            base.PostWalk(node);
        }

        public override bool Walk(BlockStatement node)
        {
            return base.Walk(node);
        }

        public override void PostWalk(BlockStatement node)
        {
            base.PostWalk(node);
        }

        public override bool Walk(NameExpression node)
        {
            if (node.TypeName == null)
            {
                ScalaFunction func;
                ScalaType tp;
                node.Scope.TryGetAnyFunc(node.Name, out func);
                node.Scope.TryGetAnyType(node.Name, out tp);

                if (func != null) node.TypeName = func.DefStatement.TypeName;
                if (tp != null) node.TypeName = tp.Name;
            }
            if (node.TypeName == null)
            {
                throw new Exception(String.Format("Unable to determine the type of the value {0} (Please Check if it exists in the Scope)", node.Name));
            }
            return false;
        }

        public override void PostWalk(NameExpression node)
        {
            base.PostWalk(node);
        }

        public override bool Walk(ApplyExpression node)
        {
            if (node.Target != null)
            {
                node.Target.Walk(this);
            }
            if (node.Args != null)
            {
                foreach (Arg arg in node.Args)
                {
                    arg.Walk(this);
                }
            }
            node.TypeName = node.Target.TypeName;
            return base.Walk(node);
        }

        public override void PostWalk(ApplyExpression node)
        {
            base.PostWalk(node);
        }

        public override bool Walk(Arg node)
        {
            node.TypeName = node.Expression.TypeName;
            Type t;
            if (node.TypeName == null || !InternalTypes.GetType(node.TypeName,node.Scope,out t))
            {
                throw new Exception(String.Format("Unable to determine the Type of the Argument {0}", node.Name));
            }
            
            return base.Walk(node);
        }

        public override void PostWalk(Arg node)
        {
            base.PostWalk(node);
        }

        public override bool Walk(SelectStatement node)
        {
            return base.Walk(node);
        }

        public override void PostWalk(SelectStatement node)
        {
            base.PostWalk(node);
        }
        #endregion
    }
}
